package repository

import (
	"context"
	"database/sql"
	"errors"
	"time"

	"gitee.com/hp-huiw/family-expense-api/internal/db"
	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	jet "github.com/go-jet/jet/v2/mysql"
	"github.com/go-jet/jet/v2/qrm"
)

type AppReleaseLog struct {
	ID          string    `db:"id"`
	ReleaseDate time.Time `db:"release_date"`
	AppVersion  string    `db:"app_version"`
	ChangeLog   string    `db:"change_log"`
	ApkFilename string    `db:"apk_filename"`
	AppStoreURL string    `db:"app_store_url"`
	IsMandatory bool      `db:"is_mandatory"`
}

type AppReleaseLogRepository interface {
	Exists(ctx context.Context) (bool, error)
	QueryByAppVersion(ctx context.Context, appVersion string) (*AppReleaseLog, error)
	MandatoryExists(ctx context.Context, appVersion string) (bool, error)
	QueryLatestVersion(ctx context.Context, appVersion string) (*AppReleaseLog, error)
	ListChangeLog(ctx context.Context, pageSize int64, pageIndex int64) ([]AppReleaseLog, error)
	Insert(ctx context.Context, model *dbmodel.AppReleaseLog) error
}

func NewAppReleaseLogRepository(db *sql.DB) AppReleaseLogRepository {
	return &appReleaseLogRepository{db: db}
}

// DeleteWithTx deletes all bank cards by account group id with transaction.
func (r *appReleaseLogRepository) Exists(ctx context.Context) (bool, error) {
	return db.Exists(ctx, r.db, tbReleaseLog, nil)
}

// QueryByAppVersion queries the app release details by its version.
func (r *appReleaseLogRepository) QueryByAppVersion(ctx context.Context, appVersion string) (*AppReleaseLog, error) {
	var query struct {
		ID          string    `db:"id"`
		ReleaseDate time.Time `db:"release_date"`
		AppVersion  string    `db:"app_version"`
		ChangeLog   string    `db:"change_log"`
		ApkFilename string    `db:"apk_filename"`
		AppStoreURL string    `db:"app_store_url"`
		IsMandatory bool      `db:"is_mandatory"`
	}

	stmt := jet.
		SELECT(
			tbReleaseLog.ReleaseDate.AS("release_date"),
			tbReleaseLog.AppVersion.AS("app_version"),
			tbReleaseLog.ApkFilename.AS("apk_filename"),
			tbReleaseLog.IsMandatory.AS("is_mandatory"),
		).
		FROM(
			tbReleaseLog,
		).
		WHERE(
			tbReleaseLog.AppVersion.EQ(jet.String(appVersion)),
		)

	err := stmt.QueryContext(ctx, r.db, &query)

	if err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			return nil, nil
		}

		return nil, err
	}

	return (*AppReleaseLog)(&query), nil
}

// MandatoryExists checks if a mandatory release exists.
func (r *appReleaseLogRepository) MandatoryExists(ctx context.Context, appVersion string) (bool, error) {
	// Different database environment causes different date type value.
	// For example, the value in database is '2019-12-31 00:00:00' but the retrieved value
	// might be '2019-12-31 14:00:00'. It might caused by time zone setting.
	// That's why using sub-query in where instead of using the input releaseDate as parameter.
	releaseDateSubquery := r.getReleaseDateSubquery(appVersion)

	condition := tbReleaseLog.ReleaseDate.GT(jet.TimestampExp(releaseDateSubquery)).
		AND(tbReleaseLog.IsMandatory.IS_TRUE())

	return db.Exists(ctx, r.db, tbReleaseLog, condition)
}

// QueryLatestVersion queries the latest version which released after the provided appVersion.
func (r *appReleaseLogRepository) QueryLatestVersion(ctx context.Context, appVersion string) (*AppReleaseLog, error) {
	// Different database environment causes different date type value.
	// For example, the value in database is '2019-12-31 00:00:00' but the retrieved value
	// might be '2019-12-31 14:00:00'. It might caused by time zone setting.
	// That's why using sub-query in where instead of using the input releaseDate as parameter.
	releaseDateSubquery := r.getReleaseDateSubquery(appVersion)

	var query struct {
		ID          string    `db:"id"`
		ReleaseDate time.Time `db:"release_date"`
		AppVersion  string    `db:"app_version"`
		ChangeLog   string    `db:"change_log"`
		ApkFilename string    `db:"apk_filename"`
		AppStoreURL string    `db:"app_store_url"`
		IsMandatory bool      `db:"is_mandatory"`
	}

	stmt := jet.
		SELECT(
			tbReleaseLog.ReleaseDate.AS("release_date"),
			tbReleaseLog.AppVersion.AS("app_version"),
			tbReleaseLog.ChangeLog.AS("change_log"),
			tbReleaseLog.ApkFilename.AS("apk_filename"),
			tbReleaseLog.AppStoreURL.AS("app_store_url"),
		).
		FROM(
			tbReleaseLog,
		).
		WHERE(
			tbReleaseLog.ReleaseDate.GT(jet.TimestampExp(releaseDateSubquery)),
		).
		ORDER_BY(
			tbReleaseLog.ReleaseDate.DESC(),
		).
		LIMIT(1)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			return nil, nil
		}

		return nil, err
	}

	return (*AppReleaseLog)(&query), nil
}

// ListChangeLog lists change log info.
func (r *appReleaseLogRepository) ListChangeLog(ctx context.Context, pageSize int64, pageIndex int64) ([]AppReleaseLog, error) {
	var query []struct {
		ID          string    `db:"id"`
		ReleaseDate time.Time `db:"release_date"`
		AppVersion  string    `db:"app_version"`
		ChangeLog   string    `db:"change_log"`
		ApkFilename string    `db:"apk_filename"`
		AppStoreURL string    `db:"app_store_url"`
		IsMandatory bool      `db:"is_mandatory"`
	}

	stmt := jet.
		SELECT(
			tbReleaseLog.ID.AS("id"),
			tbReleaseLog.ReleaseDate.AS("release_date"),
			tbReleaseLog.AppVersion.AS("app_version"),
			tbReleaseLog.ChangeLog.AS("change_log"),
			tbReleaseLog.ApkFilename.AS("apk_filename"),
			tbReleaseLog.IsMandatory.AS("is_mandatory"),
		).
		FROM(
			tbReleaseLog,
		).
		ORDER_BY(
			tbReleaseLog.ReleaseDate.DESC(),
		).
		LIMIT(pageSize).
		OFFSET(pageSize * pageIndex)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		return nil, err
	}

	result := make([]AppReleaseLog, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

// Insert inserts new model.
func (r *appReleaseLogRepository) Insert(ctx context.Context, model *dbmodel.AppReleaseLog) error {
	stmt := tbReleaseLog.
		INSERT().
		MODEL(model)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

// GetReleaseDateSubquery returns subquery for release date.
func (r *appReleaseLogRepository) getReleaseDateSubquery(appVersion string) jet.SelectStatement {
	return jet.
		SELECT(
			tbReleaseLog.ReleaseDate,
		).
		FROM(
			tbReleaseLog,
		).
		WHERE(
			tbReleaseLog.AppVersion.EQ(jet.String(appVersion)),
		)
}

type appReleaseLogRepository struct {
	db *sql.DB
}
